#include <string>
#include <vector>
#include <memory>

#include <MPSReader.hpp>

#include <coin/CoinPackedMatrix.hpp>

#include <Eigen/Sparse>

using namespace green;
using namespace std;
//-------------------------MPSReader----------------------------------------------------//

// All the pointers returned by Coin object are maintained by Coin
MPSReader::MPSReader(const string absoluteFilePath) {

  int readStatus = coinReader.readMps(absoluteFilePath.c_str());

  // TODO May be improve exception messages based on error code
  if (readStatus < 0) {
    throw runtime_error("Unable to parse file: " + absoluteFilePath);
  }
}

auto MPSReader::getProblemName() const->string {
  return coinReader.getProblemName();
}

auto MPSReader::getObjectiveName() const->string {
  return coinReader.getObjectiveName();
}

auto MPSReader::getRHSName() const->string { return coinReader.getRhsName(); }

auto MPSReader::getRangeName() const->string {
  return coinReader.getRangeName();
}

auto MPSReader::getBoundName() const->string {
  return coinReader.getBoundName();
}

auto MPSReader::getNumberOfRows() const->int { return coinReader.getNumRows(); }

auto MPSReader::getNumberOfColumns() const->int {
  return coinReader.getNumCols();
}

auto MPSReader::getNumberOfElements() const->int {
  return coinReader.getNumElements();
}

// Convert raw pointer array to vector
auto MPSReader::getColumnLowerBounds() const->vector<double> {

  vector<double> columnLower{
    coinReader.getColLower(), coinReader.getColLower() + coinReader.getNumCols()
  };
  // TODO Is move constructor invoked?
  return columnLower;
}

// Convert raw pointer array to vector
auto MPSReader::getColumnUpperBounds() const->vector<double> {

  vector<double> columnUpper{
    coinReader.getColUpper(), coinReader.getColUpper() + coinReader.getNumCols()
  };
  // TODO Is move constructor invoked?
  return columnUpper;
}

auto MPSReader::getObjectiveCoefficients() const->Eigen::SparseVector<double> {

  auto coinObjCoefficients = coinReader.getObjCoefficients();

  // XXX Code examples are taken from Eigen sparse vector test cases
  Eigen::SparseVector<double> objectiveCoefficients(coinReader.getNumCols());

  int index = 0;
  for_each(coinObjCoefficients, coinObjCoefficients + coinReader.getNumCols(),
           [&](double coefficientValue) {
    objectiveCoefficients.insertBack(index) = coefficientValue;
    ++index;
  });
  // TODO Check if copy constructor is invoked
  return objectiveCoefficients;
}

auto MPSReader::getConstraints() const->Eigen::SparseMatrix<double> {

  auto coinConstraints = coinReader.getMatrixByRow();

  // TODO By default this is ColumnMajor...Check if we have to use RowMajor for
  // performance
  // I have to understand about kind of performance I would acheive with
  // RowMajor and ColumnMajor
  Eigen::SparseMatrix<double> constraints(coinConstraints->getNumRows(),
                                          coinConstraints->getNumCols());

  // TODO Do we have to use reserve method as shown in example?
  // TODO We cant't use for each as we dont have any iterator to iterate, but
  // check again
  for (int row = 0; row < coinConstraints->getNumRows(); ++row) {
    for (int column = 0; column < coinConstraints->getNumCols(); ++column) {

      double value = coinConstraints->getCoefficient(row, column);
      if (value != 0) {
        constraints.insert(row, column) = value;
      }
    }
  }

  return constraints;
}

auto MPSReader::getRHS() const->Eigen::SparseVector<double> {

  Eigen::SparseVector<double> RHS(coinReader.getNumRows());

  const double* coinRHS = coinReader.getRightHandSide();
  // TODO Insert or InsertBack... Insert as O(1) complexity of insertion is
  // increasing order, but what is increaing order had to check
  int index = 0;
  for_each(coinRHS, coinRHS + coinReader.getNumRows(), [&](double rhsValue) {
    RHS.insertBack(index) = rhsValue;
    ++index;
  });

  return RHS;
}

auto MPSReader::getRowSense() const->vector<RowSense> {
  vector<RowSense> rowSense(coinReader.getNumRows());

  for_each(coinReader.getRowSense(),
           coinReader.getRowSense() + coinReader.getNumRows(),
           [&](char rowSenseChar) {
    if (rowSenseChar == 'L') {
      rowSense.emplace_back(RowSense::LESS_THAN);
    } else if (rowSenseChar == 'G') {
      rowSense.emplace_back(RowSense::GREATER_THAN);
    } else if (rowSenseChar == 'E') {
      rowSense.emplace_back(RowSense::EQUAL_TO);
    }
  });

  return rowSense;
}
//-------------------------MPSReader----------------------------------------------------//
